ESCOLHA, LIMPEZA E EXPLORAÇÃO DA BASE DE DADOS¶
A base de dados¶
Escolhida a base de dados que contém resultados mais recentes do IDEB (ano de 2023), disponibilizada pelo Instituto Nacional de Estudos e Pesquisas Educacionais Anísio Teixeira (Inep). Disponível em: https://www.gov.br/inep/pt-br/areas-de-atuacao/pesquisas-estatisticas-e-indicadores/ideb/resultados. Acesso em: 8 mar 2025.
Indicadores educacionais compostos por: Taxa de Aprovação, SAEB e IDEB nos anos de 2017, 2019, 20211 e 2023 e Metas do 1º ciclo do Ideb2,3. Os dados estão dimensionados por município e organizados por rede de ensino.
Os arquivos do notebook e base de dados podem ser acessado no GitHub: https://github.com/joaoleao83/notebook_joao_leao
A base de dados está armazenada em: dados/divulgacao_ensino_medio_municipios_2023/divulgacao_ensino_medio_municipios_2023.xlsx.
Limpeza dos dados¶
Limpeza feita nos arquivos com os seguintes passos:
- Ignoradas as 9 primeiras linhas e as 14 últimas linhas, por serem textos sobre a base de dados.
- Padronizado os valores '-' e 'ND' como valores NA.
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
from scipy import stats
import statsmodels.api as sm
# Read the Excel file
df = pd.read_excel(r'https://github.com/joaoleao83/notebook_joao_leao/blob/master/dados/divulgacao_ensino_medio_municipios_2023/divulgacao_ensino_medio_municipios_2023.xlsx?raw=true',
skiprows=9, skipfooter=14, na_values=['-','ND'])
#df = pd.read_excel(r'dados\divulgacao_ensino_medio_municipios_2023\divulgacao_ensino_medio_municipios_2023.xlsx',
# skiprows=9, skipfooter=14, na_values=['-','ND'])
# Display the first few rows of the dataframe
df.head(10)
| SG_UF | CO_MUNICIPIO | NO_MUNICIPIO | REDE | VL_APROVACAO_2017_SI_4 | VL_APROVACAO_2017_1 | VL_APROVACAO_2017_2 | VL_APROVACAO_2017_3 | VL_APROVACAO_2017_4 | VL_INDICADOR_REND_2017 | ... | VL_NOTA_MEDIA_2021 | VL_NOTA_MATEMATICA_2023 | VL_NOTA_PORTUGUES_2023 | VL_NOTA_MEDIA_2023 | VL_OBSERVADO_2017 | VL_OBSERVADO_2019 | VL_OBSERVADO_2021 | VL_OBSERVADO_2023 | VL_PROJECAO_2019 | VL_PROJECAO_2021 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | RO | 1100015 | Alta Floresta D'Oeste | Estadual | 88.1 | 85.0 | 90.4 | 90.4 | NaN | 0.885253 | ... | 4.326666 | 274.69 | 273.68 | 4.644526 | 3.9 | 4.5 | 3.9 | 4.5 | 4.1 | 4.3 |
| 1 | RO | 1100015 | Alta Floresta D'Oeste | Pública | 88.1 | 85.0 | 90.4 | 90.4 | NaN | 0.885253 | ... | 4.326666 | 274.69 | 273.68 | 4.644526 | 3.9 | 4.5 | 3.9 | 4.5 | 4.1 | 4.3 |
| 2 | RO | 1100023 | Ariquemes | Estadual | 82.6 | 77.9 | 83.0 | 91.7 | NaN | 0.838216 | ... | 4.437153 | 269.37 | 271.11 | 4.531334 | 3.8 | 3.9 | 4.1 | 4.1 | 4.0 | 4.2 |
| 3 | RO | 1100023 | Ariquemes | Federal | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 4 | RO | 1100023 | Ariquemes | Pública | 84.7 | 80.3 | 85.1 | 92.8 | 81.8 | 0.847368 | ... | 4.437153 | 272.01 | 274.56 | 4.620059 | 3.8 | 4.0 | 3.9 | 4.2 | 4.0 | 4.2 |
| 5 | RO | 1100031 | Cabixi | Estadual | 94.9 | 92.9 | 94.7 | 100.0 | NaN | 0.957734 | ... | 4.283235 | 262.44 | 256.32 | 4.212595 | 3.7 | NaN | 4.1 | 4.1 | 3.9 | 4.1 |
| 6 | RO | 1100031 | Cabixi | Pública | 94.9 | 92.9 | 94.7 | 100.0 | NaN | 0.957734 | ... | 4.283235 | 262.44 | 256.32 | 4.212595 | 3.7 | NaN | 4.1 | 4.1 | 3.9 | 4.1 |
| 7 | RO | 1100049 | Cacoal | Estadual | 89.1 | 84.9 | 91.1 | 93.1 | NaN | 0.895612 | ... | 4.508396 | 262.62 | 260.23 | 4.273656 | 3.7 | 4.1 | 4.2 | 3.9 | 3.9 | 4.2 |
| 8 | RO | 1100049 | Cacoal | Federal | 97.4 | 95.7 | 98.5 | 100.0 | NaN | 0.980341 | ... | NaN | 301.01 | 305.36 | 5.488440 | 5.1 | 5.9 | NaN | 5.4 | 5.3 | 5.5 |
| 9 | RO | 1100049 | Cacoal | Pública | 90.2 | 86.6 | 92.3 | 93.7 | NaN | 0.907607 | ... | 4.508396 | 270.03 | 268.93 | 4.507969 | 3.9 | 4.4 | 4.2 | 4.2 | 4.1 | 4.3 |
10 rows × 46 columns
Exibindo as variáveis disponíveis na amostra¶
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 11721 entries, 0 to 11720 Data columns (total 46 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 SG_UF 11721 non-null object 1 CO_MUNICIPIO 11721 non-null int64 2 NO_MUNICIPIO 11721 non-null object 3 REDE 11721 non-null object 4 VL_APROVACAO_2017_SI_4 11259 non-null float64 5 VL_APROVACAO_2017_1 11217 non-null float64 6 VL_APROVACAO_2017_2 11244 non-null float64 7 VL_APROVACAO_2017_3 11253 non-null float64 8 VL_APROVACAO_2017_4 1974 non-null float64 9 VL_INDICADOR_REND_2017 11256 non-null float64 10 VL_APROVACAO_2019_SI_4 11473 non-null float64 11 VL_APROVACAO_2019_1 11467 non-null float64 12 VL_APROVACAO_2019_2 11468 non-null float64 13 VL_APROVACAO_2019_3 11473 non-null float64 14 VL_APROVACAO_2019_4 1336 non-null float64 15 VL_INDICADOR_REND_2019 11473 non-null float64 16 VL_APROVACAO_2021_SI_4 11196 non-null float64 17 VL_APROVACAO_2021_1 11173 non-null float64 18 VL_APROVACAO_2021_2 11190 non-null float64 19 VL_APROVACAO_2021_3 11194 non-null float64 20 VL_APROVACAO_2021_4 1168 non-null float64 21 VL_INDICADOR_REND_2021 11195 non-null float64 22 VL_APROVACAO_2023_SI_4 11474 non-null float64 23 VL_APROVACAO_2023_1 11462 non-null float64 24 VL_APROVACAO_2023_2 11460 non-null float64 25 VL_APROVACAO_2023_3 11474 non-null float64 26 VL_APROVACAO_2023_4 851 non-null float64 27 VL_INDICADOR_REND_2023 11474 non-null float64 28 VL_NOTA_MATEMATICA_2017 10919 non-null float64 29 VL_NOTA_PORTUGUES_2017 10919 non-null float64 30 VL_NOTA_MEDIA_2017 10919 non-null float64 31 VL_NOTA_MATEMATICA_2019 9742 non-null float64 32 VL_NOTA_PORTUGUES_2019 9742 non-null float64 33 VL_NOTA_MEDIA_2019 9742 non-null float64 34 VL_NOTA_MATEMATICA_2021 6827 non-null float64 35 VL_NOTA_PORTUGUES_2021 6827 non-null float64 36 VL_NOTA_MEDIA_2021 6827 non-null float64 37 VL_NOTA_MATEMATICA_2023 10318 non-null object 38 VL_NOTA_PORTUGUES_2023 10318 non-null object 39 VL_NOTA_MEDIA_2023 10314 non-null float64 40 VL_OBSERVADO_2017 10914 non-null float64 41 VL_OBSERVADO_2019 9742 non-null float64 42 VL_OBSERVADO_2021 6826 non-null float64 43 VL_OBSERVADO_2023 10314 non-null float64 44 VL_PROJECAO_2019 10914 non-null float64 45 VL_PROJECAO_2021 11344 non-null float64 dtypes: float64(40), int64(1), object(5) memory usage: 4.1+ MB
Identificando o tipo de cada variável (qualitativa nominal, qualitativa ordinal, quantitativa discreta ou quantitativa contínua)¶
Qualitativas nominais¶
- SG_UF
- CO_MUNICIPIO (apesar de ser identificado como int64, há uma codificação específica para cada município, de modo que é possível considerar como uma variável qualitativa nominal. Sendo assim é recomendável a mudança do tipo)
- NO_MUNICIPIO
- REDE
Quantitativas contínuas¶
- Todas as demais variáveis são quantitativas contínuas, pois se referem a desempenho escolar
Convertendo as colunas SG_UF, COD_MUN, NO_MUNICIPIO, REDE para o tipo 'category' (variável qualitativa nominal)¶
df['SG_UF'] = df['SG_UF'].astype('category')
df['CO_MUNICIPIO'] = df['CO_MUNICIPIO'].astype('category')
df['NO_MUNICIPIO'] = df['NO_MUNICIPIO'].astype('category')
df['REDE'] = df['REDE'].astype('category')
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 11721 entries, 0 to 11720 Data columns (total 46 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 SG_UF 11721 non-null category 1 CO_MUNICIPIO 11721 non-null category 2 NO_MUNICIPIO 11721 non-null category 3 REDE 11721 non-null category 4 VL_APROVACAO_2017_SI_4 11259 non-null float64 5 VL_APROVACAO_2017_1 11217 non-null float64 6 VL_APROVACAO_2017_2 11244 non-null float64 7 VL_APROVACAO_2017_3 11253 non-null float64 8 VL_APROVACAO_2017_4 1974 non-null float64 9 VL_INDICADOR_REND_2017 11256 non-null float64 10 VL_APROVACAO_2019_SI_4 11473 non-null float64 11 VL_APROVACAO_2019_1 11467 non-null float64 12 VL_APROVACAO_2019_2 11468 non-null float64 13 VL_APROVACAO_2019_3 11473 non-null float64 14 VL_APROVACAO_2019_4 1336 non-null float64 15 VL_INDICADOR_REND_2019 11473 non-null float64 16 VL_APROVACAO_2021_SI_4 11196 non-null float64 17 VL_APROVACAO_2021_1 11173 non-null float64 18 VL_APROVACAO_2021_2 11190 non-null float64 19 VL_APROVACAO_2021_3 11194 non-null float64 20 VL_APROVACAO_2021_4 1168 non-null float64 21 VL_INDICADOR_REND_2021 11195 non-null float64 22 VL_APROVACAO_2023_SI_4 11474 non-null float64 23 VL_APROVACAO_2023_1 11462 non-null float64 24 VL_APROVACAO_2023_2 11460 non-null float64 25 VL_APROVACAO_2023_3 11474 non-null float64 26 VL_APROVACAO_2023_4 851 non-null float64 27 VL_INDICADOR_REND_2023 11474 non-null float64 28 VL_NOTA_MATEMATICA_2017 10919 non-null float64 29 VL_NOTA_PORTUGUES_2017 10919 non-null float64 30 VL_NOTA_MEDIA_2017 10919 non-null float64 31 VL_NOTA_MATEMATICA_2019 9742 non-null float64 32 VL_NOTA_PORTUGUES_2019 9742 non-null float64 33 VL_NOTA_MEDIA_2019 9742 non-null float64 34 VL_NOTA_MATEMATICA_2021 6827 non-null float64 35 VL_NOTA_PORTUGUES_2021 6827 non-null float64 36 VL_NOTA_MEDIA_2021 6827 non-null float64 37 VL_NOTA_MATEMATICA_2023 10318 non-null object 38 VL_NOTA_PORTUGUES_2023 10318 non-null object 39 VL_NOTA_MEDIA_2023 10314 non-null float64 40 VL_OBSERVADO_2017 10914 non-null float64 41 VL_OBSERVADO_2019 9742 non-null float64 42 VL_OBSERVADO_2021 6826 non-null float64 43 VL_OBSERVADO_2023 10314 non-null float64 44 VL_PROJECAO_2019 10914 non-null float64 45 VL_PROJECAO_2021 11344 non-null float64 dtypes: category(4), float64(40), object(2) memory usage: 4.2+ MB
Exibindo a distribuição de frequência de cada variável qualitativa¶
print(df['SG_UF'].value_counts())
print(df['REDE'].value_counts())
print(df['NO_MUNICIPIO'].value_counts())
print(df['CO_MUNICIPIO'].value_counts())
SG_UF
MG 1778
SP 1344
RS 1042
BA 881
PR 825
SC 626
GO 521
PI 466
PB 466
MA 465
PE 386
CE 385
RN 351
PA 306
MT 300
TO 282
RJ 216
AL 210
ES 177
MS 167
SE 156
AM 138
RO 112
AC 49
AP 36
RR 33
DF 3
Name: count, dtype: int64
REDE
Estadual 5559
Pública 5559
Federal 500
Municipal 103
Name: count, dtype: int64
NO_MUNICIPIO
Bom Jesus 11
Santa Inês 10
São Domingos 10
Santa Luzia 10
Bonito 9
..
Guareí 2
Guarda-Mor 2
Guaraíta 2
Guaraí 2
Óleo 2
Name: count, Length: 5287, dtype: int64
CO_MUNICIPIO
3550308 4
2925303 4
5218300 4
4300406 4
4314902 4
..
2911907 2
2911857 2
2911808 2
2911659 2
3146404 2
Name: count, Length: 5559, dtype: int64
Identificando possíveis desbalanceamentos de sua amostra¶
De acordo com as frequências exibidas, é possível perceber que:
- há Estados com maior quantidade de escolas pesquisadas em detrimento a outros;
- a grande parte das escolas pertencem à rede Estadual;
- alguns municípios têm mais escolas pesquisadas, como é o cado de Bom Jesus, com 11 escolas.
Exibindo o desbalanceamento na frequência dos Estados pesquisados¶
col = 'SG_UF'
# Compute the frequency counts of the variable and sort them in descending order
counts = df[col].value_counts().sort_values(ascending=False)
cumulative_percentage = counts.cumsum() / counts.sum() * 100
fig, ax1 = plt.subplots(figsize=(12, 6))
# Bar chart for frequency counts
ax1.bar(counts.index.astype(str), counts, color='C0')
ax1.set_xlabel(col)
ax1.set_ylabel('Frequência', color='C0')
ax1.tick_params('y', colors='C0')
plt.xticks(rotation=45)
# Cumulative percentage line using a secondary axis
ax2 = ax1.twinx()
ax2.plot(counts.index.astype(str), cumulative_percentage, color='C1', marker="D", ms=7)
ax2.set_ylabel('Porcentagem Acumulada (%)', color='C1')
ax2.tick_params('y', colors='C1')
ax2.axhline(80, color='red', linestyle='dashed') # Optional: linha de referência dos 80%
ax2.axhline(50, color='red', linestyle='dashed') # Optional: linha de referência dos 50%
plt.title(f'Gráfico de Pareto de {col}')
plt.show()
Exibindo as mesmas frequênicas dos Estados pelo gráfico do tipo pizza¶
# Create a pie chart for SG_UF distribution
plt.figure(figsize=(15, 15))
# Count state frequencies
state_counts = df['SG_UF'].value_counts()
# Create pie chart with percentages
plt.pie(state_counts, labels=state_counts.index, autopct='%1.1f%%',
shadow=True, startangle=90)
# Equal aspect ratio ensures the pie chart is circular
plt.axis('equal')
# Add title
plt.title('Distribution of Brazilian States in Dataset', fontsize=16)
# Add legend with state codes for better readability
plt.legend(state_counts.index, loc="best", bbox_to_anchor=(1, 0.5))
plt.tight_layout()
plt.show()
Conclusão do desbalanceamento dos dados em relação aos Estados¶
Pelos gráficos acima, é possível inferir que alguns poucos Estados, tais como MG, SP, RS, BA e PR, possuem maior parte das escolas representadas (50% da frequência total), o que indica um desbalanceamento no quantitativo de escolas por Estado pesquisado.
Exibindo o desbalanceamento na frequência das redes de ensino¶
col = 'REDE'
# Compute the frequency counts of the variable and sort them in descending order
counts = df[col].value_counts().sort_values(ascending=False)
cumulative_percentage = counts.cumsum() / counts.sum() * 100
fig, ax1 = plt.subplots(figsize=(12, 6))
# Bar chart for frequency counts
ax1.bar(counts.index.astype(str), counts, color='C0')
ax1.set_xlabel(col)
ax1.set_ylabel('Frequência', color='C0')
ax1.tick_params('y', colors='C0')
plt.xticks(rotation=45)
# Cumulative percentage line using a secondary axis
ax2 = ax1.twinx()
ax2.plot(counts.index.astype(str), cumulative_percentage, color='C1', marker="D", ms=7)
ax2.set_ylabel('Porcentagem Acumulada (%)', color='C1')
ax2.tick_params('y', colors='C1')
ax2.axhline(80, color='red', linestyle='dashed') # Optional: linha de referência dos 80%
ax2.axhline(50, color='red', linestyle='dashed') # Optional: linha de referência dos 50%
plt.title(f'Gráfico de Pareto de {col}')
plt.show()
Exibindo as mesmas frequênicas das redes de ensino pelo gráfico do tipo pizza¶
# Create a pie chart for SG_UF distribution
plt.figure(figsize=(15, 15))
# Count rede frequencies
rede_counts = df['REDE'].value_counts()
# Create pie chart with percentages
plt.pie(rede_counts, labels=rede_counts.index, autopct='%1.1f%%',
shadow=True, startangle=90)
# Equal aspect ratio ensures the pie chart is circular
plt.axis('equal')
# Add title
plt.title('Distributição da Rede de Ensino no Dataset', fontsize=16)
# Add legend with state codes for better readability
plt.legend(rede_counts.index, loc="best", bbox_to_anchor=(1, 0.5))
plt.tight_layout()
plt.show()
Conclusão do desbalanceamento dos dados em relação à Rede de Ensino¶
Pelos gráficos acima, é possível inferir que toda a rede pesquisada se refere à Rede Pública de ensino, sendo que a rede Estadual foi a rede bem mais pesquisada, evidenciando um desbalanceamento nesse sentido.
ESCOLHA DE UMA VARIÁVEI QUANTITATIVA PARA ANÁLISE E APLICAÇÃO DE FILTROS¶
Variável quantitativa escolhida¶
A variável VL_OBSERVADO_2023, referente ao desempenho IDEB do ano de 2023, foi a escolhida.
Aplicação de filtros¶
Para melhor representação foi feito um filtro para exibir apenas os registros nos quais a rede seja PÚBLICA, haja vista que abrange as demais redes, quais sejam: municipal, estadual e federal.
Também foram descartados os valores nulos (aplicação da função dropna), que são dados não coletados ou perdidos.
Apresentado o histograma da variável VL_OBSERVADO_2023 com descarte dos dados não disponíveis (NA, em inglês)¶
col = 'VL_OBSERVADO_2023'
df = df[df['REDE'] == 'Pública']
plt.figure(figsize=(10, 6))
plt.hist(df[col].dropna(), bins=20, edgecolor='black')
plt.title(f'Histograma de {col}')
plt.xlabel(col)
plt.ylabel('Frequência')
plt.show()
Apresentado a curva KDE da variável VL_OBSERVADO_2023 com descarte dos dados não disponíveis (NA, em inglês)¶
plt.figure(figsize=(10, 6))
df[col].plot(kind='kde')
plt.title('Densidade KDE de ' + col)
plt.xlabel(col)
plt.ylabel('Densidade')
plt.grid(True)
plt.show()
MEDIDAS DE CENTRALIDADE¶
Apresentando a moda, a média e a mediana da variável VL_OBSERVADO_2023¶
print('Média: ' + str(df[col].dropna().mean()))
print('Moda: ' + str(df[col].dropna().mode()))
print('Mediana: ' + str(df[col].dropna().median()))
Média: 4.1604343534057255 Moda: 0 4.1 Name: VL_OBSERVADO_2023, dtype: float64 Mediana: 4.2
Conclusões a partir das medidas de centralidade¶
A pequena diferença entre média, mediana e moda dos valores do IDEB 2023 indica uma distribuição aproximadamente simétrica dos dados. Essa similaridade sugere que:
- A distribuição dos resultados do IDEB segue um padrão próximo à normalidade
- Não há forte assimetria ou distorção causada por valores extremos (outliers)
- As três medidas de tendência central convergem para um valor representativo da amostra
- O valor central pode ser considerado um bom indicador do desempenho típico das escolas públicas
Esta concentração das medidas de centralidade em valores próximos também sugere certa homogeneidade nos resultados educacionais medidos, com a maioria das escolas apresentando desempenho semelhante, agrupado em torno desta centralidade.
Apresentando também as médias geométrica e harmônica¶
print('Média geométrica: ' + str(round(stats.gmean(df[col].dropna()), 2)))
print('Média harmônica: ' + str(round(stats.hmean(df[col].dropna()), 2)))
Média geométrica: 4.12 Média harmônica: 4.09
Comparação entre Média Geométrica e Média Harmônica¶
A média geométrica (3.74) apresenta um valor mais próximo das medidas de centralidade já calculadas (média aritmética, mediana e moda), o que a torna mais representativa para esta distribuição.
A média harmônica (3.66) resultou em um valor ligeiramente menor, o que é esperado, pois ela tende a ser mais influenciada pelos valores menores na distribuição.
A média geométrica é mais adequada neste contexto de avaliação educacional porque:
- Captura melhor o comportamento central dos dados quando há variações moderadas
- Penaliza menos os valores extremos inferiores do que a média harmônica
- Representa melhor dados que têm natureza multiplicativa ou de crescimento proporcional, como é o caso de indicadores educacionais ao longo do tempo
Para o IDEB, que representa um produto entre taxa de aprovação e desempenho em avaliações, a média geométrica oferece uma interpretação mais coerente com a natureza do próprio índice.
A média geométrica apresentou um valor mais próximo das medidas de centralidade (média, mediana e moda), o que sugere está mais consentânea como medida de centralidade da amostra.
Exibindo um histograma da variável VL_OBSERVADO_2023, acrescentando linhas verticais para demarcar a moda, a média e a mediana¶
# Get the data for our chosen variable - making sure to filter for Public schools and drop NAs
data = df[df['REDE'] == 'Pública'][col].dropna()
# Calculate the mode, mean and median
mode_val = data.mode().iloc[0]
mean = data.mean()
median_val = data.median()
# Create the histogram with vertical lines
plt.figure(figsize=(10, 6))
plt.hist(data, bins=20, edgecolor='black', alpha=0.7)
# Add vertical lines for mode, mean and median
plt.axvline(mode_val, color='blue', linestyle='dashed', linewidth=2, label=f'Moda: {mode_val:.2f}')
plt.axvline(mean, color='green', linestyle='dashed', linewidth=2, label=f'Média: {mean:.2f}')
plt.axvline(median_val, color='gray', linestyle='dashed', linewidth=2, label=f'Mediana: {median_val:.2f}')
# Add labels and title
plt.title(f'Histograma de {col}')
plt.xlabel(col)
plt.ylabel('Frequência')
plt.legend()
plt.grid(True, linestyle='--', alpha=0.7)
plt.show()
Apresentando um gráfico do tipo BOX PLOT para a variável VL_OBSERVADO_2023.¶
plt.figure(figsize=(10, 6))
plt.boxplot(df[col].dropna(), vert=True, patch_artist=True, boxprops=dict(facecolor='lightblue'))
plt.title(f'Box Plot de {col}')
plt.ylabel('Valor')
plt.grid(True, linestyle='--', alpha=0.7)
# Calculate and display key statistics
data = df[col].dropna()
q1 = data.quantile(0.25)
q3 = data.quantile(0.75)
iqr = q3 - q1
lower_limit = q1 - 1.5 * iqr
upper_limit = q3 + 1.5 * iqr
outliers = data[(data < lower_limit) | (data > upper_limit)]
# Count upper and lower outliers separately
upper_outliers = data[data > upper_limit]
lower_outliers = data[data < lower_limit]
plt.text(1.1, data.min(), f'Min: {data.min():.1f}', verticalalignment='center')
plt.text(1.1, q1, f'Q1: {q1:.1f}', verticalalignment='center')
plt.text(1.1, data.median(), f'Median: {data.median():.1f}', verticalalignment='center')
plt.text(1.1, q3, f'Q3: {q3:.1f}', verticalalignment='center')
plt.text(1.1, data.max(), f'Max: {data.max():.1f}', verticalalignment='center')
plt.text(1.1, data.mean(), f'Mean: {data.mean():.1f}', color='red', verticalalignment='center')
plt.text(0.8, upper_limit, f'Upper limit: {upper_limit:.1f}', horizontalalignment='right')
plt.text(0.8, lower_limit, f'Lower limit: {lower_limit:.1f}', horizontalalignment='right')
plt.text(0.8, data.max() * 0.95, f'Total outliers: {len(outliers)}', horizontalalignment='right')
plt.text(0.8, data.max() * 0.90, f'Upper outliers: {len(upper_outliers)}', horizontalalignment='right')
plt.text(0.8, data.max() * 0.85, f'Lower outliers: {len(lower_outliers)}', horizontalalignment='right')
plt.show()
Análise dos Outliers no IDEB 2023 (VL_OBSERVADO_2023)¶
Caracterização dos Outliers¶
O box plot revela a presença de 52 outliers (aproximadamente 1% da amostra), sendo 25 acima do limite superior (5.55) e 27 abaixo do limite inferior (2.75). Esta distribuição relativamente equilibrada de valores extremos sugere que existem tanto escolas com desempenho excepcionalmente alto quanto excepcionalmente baixo em relação ao padrão nacional.
Interpretação Educacional¶
- Outliers superiores: Representam escolas que conseguiram resultados significativamente acima da tendência nacional, com IDEB chegando a 7.1. Estas "ilhas de excelência" podem servir como modelos de boas práticas educacionais.
- Outliers inferiores: Indicam escolas em situação crítica, com IDEB tão baixo quanto 1.9, sinalizando necessidade urgente de intervenção pedagógica e políticas públicas específicas.
Significado Estatístico¶
A presença equilibrada de outliers em ambas as extremidades, mas com concentração dos dados entre 3.8 (Q1) e 4.5 (Q3), demonstra que o sistema educacional brasileiro possui casos excepcionais que fogem ao padrão predominante de desempenho médio.
A análise destes outliers pode revelar informações valiosas sobre os fatores contextuais que promovem ou inibem o sucesso educacional no ensino médio brasileiro.
MEDIDAS DE DISPERSÃO¶
Apresentando medidas de dispersão da variável¶
data = df[col].dropna()
# Cálculo das medidas de dispersão
variancia = data.var() # Variância amostral
desvio_padrao = data.std() # Desvio padrão amostral
amplitude = data.max() - data.min() # Amplitude
q1 = data.quantile(0.25) # Primeiro quartil
q3 = data.quantile(0.75) # Terceiro quartil
iqr = q3 - q1 # Intervalo interquartílico (IQR)
coef_disp_quartil = iqr / (q1 + q3) # Coeficiente de dispersão quartílica
coef_var = desvio_padrao / data.mean() # Coeficiente de variação
print("Variância (amostral):", round(variancia, 2))
print("Desvio Padrão (amostral):", round(desvio_padrao, 2))
print("Amplitude:", round(amplitude, 2))
print("Intervalo Interquartílico (IQR):", round(iqr, 2))
print("Coeficiente de Dispersão Quartílica:", round(coef_disp_quartil, 2))
print("Coeficiente de Variação:", round(coef_var, 2))
Variância (amostral): 0.29 Desvio Padrão (amostral): 0.54 Amplitude: 5.2 Intervalo Interquartílico (IQR): 0.7 Coeficiente de Dispersão Quartílica: 0.08 Coeficiente de Variação: 0.13
Análise das Medidas de Dispersão para VL_OBSERVADO_2023¶
1.1. A amostra é considerada concentrada, pois apresenta um coeficiente de variação relativamente baixo (13%) e um desvio padrão (0.54) pequeno em relação à média (~4.16). Isso indica que os valores do IDEB 2023 tendem a se agrupar próximos à média, com pouca variabilidade relativa.¶
1.2. A amplitude interna (IIQ) não é proporcional à amplitude total da amostra. O intervalo interquartílico (IIQ = 0.7) representa apenas cerca de 13.5% da amplitude total (5.2). Isso demonstra que, enquanto os 50% centrais dos dados estão concentrados em uma faixa estreita, os valores extremos se estendem consideravelmente além desse intervalo central.¶
1.3. Implicações no contexto educacional:¶
- A concentração dos valores sugere que a maioria das escolas públicas apresenta desempenho similar no IDEB 2023, com pequenas variações em torno da média.
- A grande diferença entre o IIQ e a amplitude total indica a presença de outliers significativos - tanto escolas com desempenho excepcionalmente alto quanto excepcionalmente baixo.
- As políticas educacionais precisam considerar essa distribuição: enquanto a maioria das escolas requer intervenções semelhantes, os casos extremos (especialmente os de baixo desempenho) podem necessitar de abordagens específicas.
- Os valores extremos podem representar tanto casos de sucesso a serem estudados como modelos (escolas com IDEB elevado) quanto situações críticas que demandam atenção urgente (escolas com IDEB muito baixo).
Exibindo gráfico de dispersão (KDE) da variável estudada, sinalizando medidas de dispersão¶
plt.figure(figsize=(12, 6))
sns.kdeplot(data, fill=True)
# Mean and standard deviation lines in blue
mean = data.mean()
plt.axvline(mean, color='blue', linestyle='-', linewidth=2, label='Média')
plt.axvline(mean - desvio_padrao, color='blue', linestyle='--', linewidth=2, label='Média - 1 Desvio Padrão')
plt.axvline(mean + desvio_padrao, color='blue', linestyle='--', linewidth=2, label='Média + 1 Desvio Padrão')
# Q1 and Q3 lines in gray
plt.axvline(q1, color='gray', linestyle='--', linewidth=2, label='Q1')
plt.axvline(q3, color='gray', linestyle='--', linewidth=2, label='Q3')
plt.title(f'KDE da variável {col}')
plt.xlabel(f'{col}')
plt.ylabel('Densidade')
plt.legend()
plt.show()
Exibindo gráfico de dispersão (KDE) da variável estudada, sinalizando outliers¶
# Calcular os limites para identificar outliers usando o método IQR
lower_limit = q1 - 1.5 * iqr
upper_limit = q3 + 1.5 * iqr
# Identificar os outliers na variável IDEB12_17
outliers = data[(data < lower_limit) | (data > upper_limit)]
plt.figure(figsize=(12, 6))
sns.kdeplot(data, fill=True, label='KDE de IDEB12_17')
# Marcar os limites do IQR (opcional para visualização)
plt.axvline(lower_limit, color='gray', linestyle='--', linewidth=2, label='Limite Inferior')
plt.axvline(upper_limit, color='gray', linestyle='--', linewidth=2, label='Limite Superior')
# Se existirem outliers, marque-os com rugplot para indicá-los na frequência
if not outliers.empty:
sns.rugplot(outliers, color='red', height=0.05, label='Outliers')
plt.title(f'KDE de {col} com marcação de Outliers')
plt.xlabel(f'{col}')
plt.ylabel('Densidade')
plt.legend()
plt.show()
Apresentando medidas de assimetria e curtose¶
# Calculate asymmetry (skewness) measures for VL_OBSERVADO_2023
# Skewness measures
skewness_fisher = stats.skew(data)
skewness_pearson1 = (mean - mode_val) / desvio_padrao
skewness_pearson2 = 3 * (mean - median_val) / desvio_padrao
skewness_bowley = (q3 + q1 - 2 * median_val) / iqr
# Kurtosis
kurtosis = stats.kurtosis(data)
print("Medidas de Assimetria:")
print(f"Coeficiente de Fisher-Pearson: {skewness_fisher:.4f}")
print(f"Primeiro Coeficiente de Pearson: {skewness_pearson1:.4f}")
print(f"Segundo Coeficiente de Pearson: {skewness_pearson2:.4f}")
print(f"Coeficiente de Bowley: {skewness_bowley:.4f}")
print("\nMedida de Curtose:")
print(f"Excesso de Curtose: {kurtosis:.4f}")
# Interpretations
print("\nInterpretação da Assimetria:")
if abs(skewness_fisher) < 0.1:
print("A distribuição é aproximadamente simétrica")
elif skewness_fisher < -0.1:
print("A distribuição possui assimetria negativa (cauda à esquerda)")
else:
print("A distribuição possui assimetria positiva (cauda à direita)")
print("\nInterpretação da Curtose:")
if abs(kurtosis) < 0.1:
print("A distribuição é mesocúrtica (similar à normal)")
elif kurtosis < -0.1:
print("A distribuição é platicúrtica (mais achatada que a normal)")
else:
print("A distribuição é leptocúrtica (mais pontiaguda que a normal)")
Medidas de Assimetria: Coeficiente de Fisher-Pearson: -0.0121 Primeiro Coeficiente de Pearson: 0.1116 Segundo Coeficiente de Pearson: -0.2192 Coeficiente de Bowley: -0.1429 Medida de Curtose: Excesso de Curtose: 0.4030 Interpretação da Assimetria: A distribuição é aproximadamente simétrica Interpretação da Curtose: A distribuição é leptocúrtica (mais pontiaguda que a normal)
ASSOCIAÇÃO E CORRELAÇÃO ENTRE VARIÁVEIS¶
Apresentando gráfico de dispersão do tipo KDE, da variável VL_OBSERVADO_2023, particionado pela variável SG_UF¶
# Get counts of records per state
state_counts = df['SG_UF'].value_counts()
# Select the top 6 states by count for better visualization
top_states = state_counts.head(6).index.tolist()
# Create a figure for the plot
plt.figure(figsize=(12, 8))
# Create KDE plots for the top states
for state in top_states:
state_data = df[df['SG_UF'] == state][col].dropna()
if len(state_data) > 0: # Make sure we have data
sns.kdeplot(state_data, label=f'{state} (n={len(state_data)})', fill=True, alpha=0.2)
# Add overall KDE for reference
sns.kdeplot(data, color='black', label='All States', linewidth=2)
# Add vertical line for the overall mean
plt.axvline(data.mean(), color='black', linestyle='--',
label=f'Overall Mean: {data.mean():.2f}', linewidth=1.5)
# Customize the plot
plt.title(f'KDE of {col} by State (Top 6 States by Sample Size)', fontsize=14)
plt.xlabel(f'{col} (IDEB Score)', fontsize=12)
plt.ylabel('Density', fontsize=12)
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend(title='State')
# Show plot
plt.tight_layout()
plt.show()
Apresentando gráfico de dispersão do tipo scatter plot das variáveis VL_NOTA_MATEMATICA_2023 e VL_NOTA_PORTUGUES_2023¶
# Convert columns to numeric, coercing any non-numeric values to NaN
df['VL_NOTA_MATEMATICA_2023'] = pd.to_numeric(df['VL_NOTA_MATEMATICA_2023'], errors='coerce')
df['VL_NOTA_PORTUGUES_2023'] = pd.to_numeric(df['VL_NOTA_PORTUGUES_2023'], errors='coerce')
# Create scatter plot
plt.figure(figsize=(10, 6))
sns.scatterplot(data=df, x='VL_NOTA_MATEMATICA_2023', y='VL_NOTA_PORTUGUES_2023', alpha=0.6)
# Add regression line
sns.regplot(data=df,
x='VL_NOTA_MATEMATICA_2023',
y='VL_NOTA_PORTUGUES_2023',
scatter=False,
line_kws={"color": "red"})
# Calculate correlation coefficient
corr = df['VL_NOTA_MATEMATICA_2023'].corr(df['VL_NOTA_PORTUGUES_2023'])
# Add title and labels
plt.title(f'Scatter Plot: Math vs Portuguese Scores (2023)\nCorrelation: {corr:.4f}', fontsize=14)
plt.xlabel('Math Score (2023)', fontsize=12)
plt.ylabel('Portuguese Score (2023)', fontsize=12)
plt.grid(True, linestyle='--', alpha=0.7)
plt.tight_layout()
plt.show()
Conclusão sobre a correlaçao entre as variáveis VL_NOTA_MATEMATICA_2023 e VL_NOTA_PORTUGUES_2023¶
Pelo gráfico Scatter Plot é possível perceber forte correlação entre as notas associadas às disciplinas de Português e Matemática, a indicar que um aluno que tende a ter um bom desempenho em uma das disciplinas também tende a ter um bom desempenho na outra.
Apresentando gráficos do tipo violin para a variável VL_OBSERVADO_2023, subdividido pela variável SG_UF¶
# Create a violin plot of VL_OBSERVADO_2023 by state
plt.figure(figsize=(20, 10))
# Get data with no NAs for VL_OBSERVADO_2023
valid_data = df.dropna(subset=['VL_OBSERVADO_2023'])
# Get median values per state for sorting and calculate sample sizes
state_stats = valid_data.groupby('SG_UF')['VL_OBSERVADO_2023'].agg(['median', 'count']).sort_values(by='median', ascending=False)
state_order = state_stats.index.tolist()
# Create the violin plot with all states, sorted by median
# Note: For seaborn v0.14.0+, use: hue='SG_UF', legend=False instead of palette directly
sns.violinplot(x='SG_UF', y='VL_OBSERVADO_2023', data=valid_data,
order=state_order, palette='viridis', inner='quartile')
# Add horizontal line for overall median
plt.axhline(y=median_val, color='red', linestyle='--', alpha=0.7, label=f'Overall Median: {median_val:.1f}')
# Add sample size to the x-tick labels
count_values = state_stats['count'].values
plt.xticks(ticks=range(len(state_order)),
labels=[f"{state}\nn={count}" for state, count in zip(state_order, count_values)],
rotation=45, ha='right')
# Add grid for better readability
plt.grid(True, axis='y', linestyle='--', alpha=0.7)
# Customize the plot
plt.title('Distribution of IDEB 2023 Scores by State (Sorted by Median)', fontsize=18)
plt.xlabel('State', fontsize=16)
plt.ylabel('IDEB 2023 Score', fontsize=16)
plt.legend()
# Show the plot
plt.tight_layout()
plt.show()
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\1343659163.py:8: FutureWarning: The default of observed=False is deprecated and will be changed to True in a future version of pandas. Pass observed=False to retain current behavior or observed=True to adopt the future default and silence this warning.
state_stats = valid_data.groupby('SG_UF')['VL_OBSERVADO_2023'].agg(['median', 'count']).sort_values(by='median', ascending=False)
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\1343659163.py:13: FutureWarning:
Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect.
sns.violinplot(x='SG_UF', y='VL_OBSERVADO_2023', data=valid_data,
Análise das Distribuições do IDEB 2023 por Estado¶
Desigualdades Regionais¶
O gráfico de violino revela claras disparidades educacionais entre os estados brasileiros. Estados como Espírito Santo, Goiás e Paraná apresentam medianas superiores a 4.7, enquanto estados como Amazonas, Amapá e Roraima mostram medianas abaixo de 3.5, evidenciando uma significativa desigualdade regional no desempenho educacional.
Padrões de Distribuição¶
- Estados do Sul e Sudeste: Predominantemente posicionados no topo do ranking com distribuições mais elevadas.
- Estados do Norte e Nordeste: Geralmente apresentam medianas mais baixas, com exceções como Tocantins.
- Variabilidade interna: Estados como São Paulo e Minas Gerais mostram grande variabilidade interna (violinos mais largos), indicando heterogeneidade entre suas escolas.
Consistência vs. Desigualdade¶
Alguns estados apresentam distribuições mais compactas (violinos estreitos), sugerindo consistência no desempenho entre escolas. Outros mostram distribuições mais amplas, indicando maior desigualdade educacional dentro do próprio estado.
Implicações para Políticas Educacionais¶
Esta análise sugere a necessidade de políticas educacionais diferenciadas: enquanto alguns estados necessitam de elevação geral do patamar educacional, outros precisam focalizar na redução das desigualdades entre suas escolas.
# Definir os limites para os atributos
q1 = data.quantile(0.25)
q3 = data.quantile(0.75)
# Criar a nova variável categórica
df['rendimento'] = pd.cut(df[f'{col}'], bins=[-float('inf'), q1, q3, float('inf')], labels=['rendimento baixo', 'rendimento médio', 'rendimento alto'])
# Exibir as primeiras linhas do dataframe para verificar a nova variável
df[[f'{col}', 'rendimento']].head(10)
# Tabela de contingência
contingency_table = pd.crosstab(df['SG_UF'], df['rendimento'])
# Calcular o total de observações
n = contingency_table.sum().sum()
# Qui-quadrado de independência
chi2, p, dof, expected = stats.chi2_contingency(contingency_table)
# Coeficiente de contingência
contingency_coefficient = np.sqrt(chi2 / (chi2 + n))
# Coeficiente Phi
phi_coefficient = np.sqrt(chi2 / n)
# V de Cramer
v_cramer = np.sqrt(chi2 / (n * (min(contingency_table.shape) - 1)))
# Exibir os resultados
print("Tabela de Contingência:\n", contingency_table)
print("\nQui-quadrado: ", chi2)
print("p-valor: ", p)
print("Grau de liberdade: ", dof)
print("Valores esperados:\n", expected)
print("\nCoeficiente de Contingência: ", contingency_coefficient)
print("Coeficiente Phi: ", phi_coefficient)
print("V de Cramer: ", v_cramer)
Tabela de Contingência: rendimento rendimento baixo rendimento médio rendimento alto SG_UF AC 13 7 0 AL 37 56 4 AM 56 4 1 AP 11 2 0 BA 237 159 13 CE 3 96 85 DF 1 0 0 ES 1 13 59 GO 2 77 150 MA 155 49 2 MG 233 410 137 MS 29 25 4 MT 24 92 16 PA 42 96 5 PB 93 93 22 PE 2 100 81 PI 41 138 36 PR 3 117 267 RJ 48 33 8 RN 94 29 5 RO 9 38 4 RR 13 0 0 RS 82 180 122 SC 76 84 28 SE 48 23 1 SP 41 408 158 TO 54 74 6 Qui-quadrado: 2231.461965595743 p-valor: 0.0 Grau de liberdade: 52 Valores esperados: [[5.71767029e+00 9.48864758e+00 4.79368213e+00] [2.77307009e+01 4.60199408e+01 2.32493583e+01] [1.74388944e+01 2.89403751e+01 1.46207305e+01] [3.71648569e+00 6.16762093e+00 3.11589339e+00] [1.16926357e+02 1.94042843e+02 9.80307996e+01] [5.26025666e+01 8.72955577e+01 4.41018756e+01] [2.85883514e-01 4.74432379e-01 2.39684107e-01] [2.08694965e+01 3.46335637e+01 1.74969398e+01] [6.54673248e+01 1.08645015e+02 5.48876604e+01] [5.88920039e+01 9.77330701e+01 4.93749260e+01] [2.22989141e+02 3.70057256e+02 1.86953603e+02] [1.65812438e+01 2.75170780e+01 1.39016782e+01] [3.77366239e+01 6.26250740e+01 3.16383021e+01] [4.08813425e+01 6.78438302e+01 3.42748272e+01] [5.94637710e+01 9.86819348e+01 4.98542942e+01] [5.23166831e+01 8.68211254e+01 4.38621915e+01] [6.14649556e+01 1.02002962e+02 5.15320829e+01] [1.10636920e+02 1.83605331e+02 9.27577493e+01] [2.54436328e+01 4.22244817e+01 2.13318855e+01] [3.65930898e+01 6.07273445e+01 3.06795656e+01] [1.45800592e+01 2.41960513e+01 1.22238894e+01] [3.71648569e+00 6.16762093e+00 3.11589339e+00] [1.09779269e+02 1.82182034e+02 9.20386969e+01] [5.37461007e+01 8.91932873e+01 4.50606120e+01] [2.05836130e+01 3.41591313e+01 1.72572557e+01] [1.73531293e+02 2.87980454e+02 1.45488253e+02] [3.83083909e+01 6.35739388e+01 3.21176703e+01]] Coeficiente de Contingência: 0.5530171286437212 Coeficiente Phi: 0.6637507419970448 V de Cramer: 0.4693426506837129
Análise dos Coeficientes de Associação - Estado vs. Rendimento Educacional¶
Força da Associação¶
Os coeficientes calculados revelam uma associação moderada a forte entre os estados brasileiros e o rendimento educacional:
- V de Cramer: 0.469 - Indica uma associação substancial, demonstrando que aproximadamente 47% da variação no desempenho educacional pode ser explicada pela variável estado
- Coeficiente de Contingência: 0.553 - Confirma a associação moderadamente forte entre as variáveis
- Coeficiente Phi: 0.664 - Sugere uma correlação considerável entre o estado e o rendimento escolar
Significância Estatística¶
O p-valor extremamente baixo (p ≈ 0) e o alto valor de qui-quadrado (2231.46) confirmam que a associação entre estados e desempenho educacional é estatisticamente significativa, rejeitando fortemente a hipótese de independência entre estas variáveis.
Implicações Educacionais¶
Esta forte associação revela um cenário de desigualdade regional na educação brasileira:
- O desempenho educacional está significativamente vinculado à localização geográfica
- Estados apresentam perfis educacionais distintos, com alguns concentrando escolas de alto rendimento e outros predominantemente de baixo rendimento
- As políticas educacionais precisam considerar estas disparidades regionais, desenvolvendo estratégias específicas para cada contexto estadual
Esta análise complementa o observado nos gráficos de violino, confirmando quantitativamente que o fator geográfico (estado) é determinante para o desempenho educacional no Brasil.
Calculando medidas de correlação entres as variáveis VL_NOTA_MATEMATICA_2023 e VL_NOTA_PORTUGUES_2023¶
# Calculate covariance between math and Portuguese scores
covariance = df['VL_NOTA_MATEMATICA_2023'].cov(df['VL_NOTA_PORTUGUES_2023'])
# Calculate Pearson correlation
pearson_corr = df['VL_NOTA_MATEMATICA_2023'].corr(df['VL_NOTA_PORTUGUES_2023'], method='pearson')
# Calculate Spearman correlation
spearman_corr = df['VL_NOTA_MATEMATICA_2023'].corr(df['VL_NOTA_PORTUGUES_2023'], method='spearman')
# Print results with rounded values for better readability
print(f"Covariância: {covariance:.2f}")
print(f"Correlação de Pearson: {pearson_corr:.2f}")
print(f"Correlação de Spearman: {spearman_corr:.2f}")
Covariância: 251.50 Correlação de Pearson: 0.88 Correlação de Spearman: 0.89
# Define a function to calculate correlation metrics for a group
def calculate_correlations(group):
# Check if there are enough data points for calculation
if len(group) > 1: # Need at least 2 points for correlation
cov = group['VL_NOTA_MATEMATICA_2023'].cov(group['VL_NOTA_PORTUGUES_2023'])
pearson = group['VL_NOTA_MATEMATICA_2023'].corr(group['VL_NOTA_PORTUGUES_2023'], method='pearson')
spearman = group['VL_NOTA_MATEMATICA_2023'].corr(group['VL_NOTA_PORTUGUES_2023'], method='spearman')
count = len(group)
return pd.Series({'Covariância': cov, 'Correlação de Pearson': pearson,
'Correlação de Spearman': spearman, 'Contagem': count})
else:
return pd.Series({'Covariância': np.nan, 'Correlação de Pearson': np.nan,
'Correlação de Spearman': np.nan, 'Contagem': len(group)})
# Group by state and apply the function
correlation_by_state = df.groupby('SG_UF').apply(calculate_correlations)
# Sort by Pearson correlation in descending order
correlation_by_state = correlation_by_state.sort_values('Correlação de Pearson', ascending=False)
# Display the results
print("Correlação entre notas de Matemática e Português por Estado (2023):")
print(correlation_by_state.round(2))
# Plot the Pearson correlation by state
plt.figure(figsize=(14, 8))
correlation_by_state['Correlação de Pearson'].plot(kind='bar')
plt.title('Correlação de Pearson entre Notas de Matemática e Português por Estado (2023)')
plt.ylabel('Coeficiente de Correlação')
plt.xlabel('Estado')
plt.grid(axis='y', linestyle='--', alpha=0.7)
plt.tight_layout()
plt.show()
Correlação entre notas de Matemática e Português por Estado (2023):
Covariância Correlação de Pearson Correlação de Spearman Contagem
SG_UF
RR 179.32 0.95 0.84 15.0
AP 128.11 0.95 0.88 16.0
RJ 126.45 0.92 0.93 92.0
ES 156.76 0.88 0.87 78.0
RN 192.61 0.88 0.84 166.0
MA 103.86 0.88 0.87 217.0
BA 140.46 0.88 0.88 417.0
MG 184.59 0.87 0.88 852.0
RO 93.15 0.86 0.83 52.0
MS 83.28 0.86 0.83 79.0
PA 93.18 0.86 0.88 144.0
AC 81.70 0.85 0.83 22.0
PE 117.71 0.85 0.82 185.0
PI 184.15 0.84 0.83 224.0
SE 112.25 0.83 0.85 75.0
AM 153.77 0.82 0.89 62.0
PB 176.13 0.82 0.82 223.0
SC 151.61 0.81 0.76 295.0
SP 102.91 0.81 0.81 645.0
PR 102.76 0.81 0.81 399.0
TO 124.65 0.79 0.79 137.0
GO 163.49 0.78 0.75 246.0
MT 118.56 0.78 0.78 141.0
RS 182.72 0.78 0.76 494.0
AL 154.21 0.77 0.86 98.0
CE 149.13 0.75 0.88 184.0
DF NaN NaN NaN 1.0
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\4013902137.py:16: FutureWarning: The default of observed=False is deprecated and will be changed to True in a future version of pandas. Pass observed=False to retain current behavior or observed=True to adopt the future default and silence this warning.
correlation_by_state = df.groupby('SG_UF').apply(calculate_correlations)
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\4013902137.py:16: DeprecationWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
correlation_by_state = df.groupby('SG_UF').apply(calculate_correlations)
Análise das Medidas de Correlação entre Notas de Matemática e Português (2023)¶
Correlação Nacional¶
As medidas de correlação entre as notas de Matemática e Português do SAEB 2023 revelam uma associação fortemente positiva entre estas disciplinas:
- Correlação de Pearson: 0.88 - Indica uma relação linear muito forte
- Correlação de Spearman: 0.89 - Confirma que a relação é consistentemente forte em diferentes níveis de desempenho
- Covariância: 251.50 - Demonstra uma substancial variação conjunta entre as disciplinas
Variações Estaduais¶
A análise por estado revela padrões interessantes:
- Estados com correlação extremamente alta (r > 0.92): Roraima, Amapá e Rio de Janeiro
- Estados com correlação relativamente menor (r < 0.78): Ceará, Rio Grande do Sul e Mato Grosso
Implicações Educacionais¶
A forte correlação entre as disciplinas sugere que:
- O desenvolvimento de competências em língua portuguesa e matemática está intrinsecamente conectado
- Estudantes tendem a ter desempenho semelhante em ambas as áreas
- Fatores estruturais e socioeconômicos provavelmente afetam ambas as disciplinas de maneira similar
- Intervenções pedagógicas eficazes podem potencialmente beneficiar ambas as áreas de conhecimento simultaneamente
Esta relação reforça a importância de abordagens educacionais integradas que desenvolvam habilidades fundamentais aplicáveis a múltiplos campos do conhecimento.
REGRESSÃO LINEAR¶
Cálculo manual da regressão linear das variáveis VL_NOTA_MATEMATICA_2023 e VL_NOTA_PORTUGUES_2023¶
# Armazena os valores de VL_NOTA_MATEMATICA_2023 e VL_NOTA_PORTUGUES_2023
x = df['VL_NOTA_MATEMATICA_2023'].dropna()
y = df['VL_NOTA_PORTUGUES_2023'].dropna()
# Adiciona intercepto
X = sm.add_constant(x)
# Calcular médias e desvios padrão
mean_X = np.mean(x)
mean_y = np.mean(y)
print(f"Média de VL_NOTA_MATEMATICA_2023: {mean_X}")
print(f"Média de VL_NOTA_PORTUGUES_2023 {mean_y}")
sd_X = np.std(x, ddof=1)
sd_y = np.std(y, ddof=1)
print(f"Desvio padrão de VL_NOTA_MATEMATICA_2023: {sd_X}")
print(f"Desvio padrão de VL_NOTA_PORTUGUES_2023: {sd_y}")
# Calcular correlação e coeficientes
correlation = np.corrcoef(x, y)[0, 1]
print("Correlação entre VL_NOTA_MATEMATICA_2023 e VL_NOTA_PORTUGUES_2023")
slope = (correlation * sd_y) / sd_X
intercept = mean_y - slope * mean_X
print(f"Y = {slope}X + {intercept}")
print("\n")
plt.scatter(x, y, label='Dados reais')
y_pred = (slope * x) + intercept
plt.plot(x, y_pred, color='red', label='Regressão Linear')
plt.xlabel('Variável Independente: VL_NOTA_MATEMATICA_2023')
plt.ylabel('Variável Dependente: VL_NOTA_PORTUGUES_2023')
plt.title('Regressão Linear Simples')
plt.legend()
plt.show()
Média de VL_NOTA_MATEMATICA_2023: 267.9328390918065 Média de VL_NOTA_PORTUGUES_2023 269.8174669299112 Desvio padrão de VL_NOTA_MATEMATICA_2023: 16.737259364116156 Desvio padrão de VL_NOTA_PORTUGUES_2023: 17.055635450842956 Correlação entre VL_NOTA_MATEMATICA_2023 e VL_NOTA_PORTUGUES_2023 Y = 0.8977662956196751X + 29.276394503597572
Cálculo Manual da regressão linear invertendo as variáveis dependentes e independentes¶
# Armazena os valores de VL_NOTA_MATEMATICA_2023 e VL_NOTA_PORTUGUES_2023
y = df['VL_NOTA_MATEMATICA_2023'].dropna()
x = df['VL_NOTA_PORTUGUES_2023'].dropna()
# Adiciona intercepto
X = sm.add_constant(x)
# Calcular médias e desvios padrão
mean_X = np.mean(x)
mean_y = np.mean(y)
print(f"Média de VL_NOTA_MATEMATICA_2023: {mean_X}")
print(f"Média de VL_NOTA_PORTUGUES_2023 {mean_y}")
sd_X = np.std(x, ddof=1)
sd_y = np.std(y, ddof=1)
print(f"Desvio padrão de VL_NOTA_MATEMATICA_2023: {sd_X}")
print(f"Desvio padrão de VL_NOTA_PORTUGUES_2023: {sd_y}")
# Calcular correlação e coeficientes
correlation = np.corrcoef(x, y)[0, 1]
print("Correlação entre VL_NOTA_MATEMATICA_2023 e VL_NOTA_PORTUGUES_2023")
slope = (correlation * sd_y) / sd_X
intercept = mean_y - slope * mean_X
print(f"Y = {slope}X + {intercept}")
print("\n")
plt.scatter(x, y, label='Dados reais')
y_pred = (slope * x) + intercept
plt.plot(x, y_pred, color='red', label='Regressão Linear')
plt.xlabel('Variável Independente: VL_NOTA_MATEMATICA_2023')
plt.ylabel('Variável Dependente: VL_NOTA_PORTUGUES_2023')
plt.title('Regressão Linear Simples')
plt.legend()
plt.show()
Média de VL_NOTA_MATEMATICA_2023: 269.8174669299112 Média de VL_NOTA_PORTUGUES_2023 267.9328390918065 Desvio padrão de VL_NOTA_MATEMATICA_2023: 17.055635450842956 Desvio padrão de VL_NOTA_PORTUGUES_2023: 16.737259364116156 Correlação entre VL_NOTA_MATEMATICA_2023 e VL_NOTA_PORTUGUES_2023 Y = 0.8645620724460227X + 34.6588907007463
Modelo OLS¶
# Estimação por OLS (que é MLE sob erro normal)
model = sm.OLS(y, X).fit()
print(model.summary())
print("\n")
# Fazer previsões
y_pred = model.predict(X)
# Visualizar resultados
plt.scatter(x, y, label='Dados reais')
plt.plot(x, y_pred, color='red', label='Regressão Linear')
plt.xlabel('Variável Independente')
plt.ylabel('Variável Dependente')
plt.legend()
plt.show()
OLS Regression Results
===================================================================================
Dep. Variable: VL_NOTA_MATEMATICA_2023 R-squared: 0.776
Model: OLS Adj. R-squared: 0.776
Method: Least Squares F-statistic: 1.756e+04
Date: dom, 09 mar 2025 Prob (F-statistic): 0.00
Time: 15:49:24 Log-Likelihood: -17667.
No. Observations: 5065 AIC: 3.534e+04
Df Residuals: 5063 BIC: 3.535e+04
Df Model: 1
Covariance Type: nonrobust
==========================================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------------------
const 34.6589 1.764 19.648 0.000 31.201 38.117
VL_NOTA_PORTUGUES_2023 0.8646 0.007 132.504 0.000 0.852 0.877
==============================================================================
Omnibus: 1956.523 Durbin-Watson: 1.787
Prob(Omnibus): 0.000 Jarque-Bera (JB): 27234.086
Skew: 1.455 Prob(JB): 0.00
Kurtosis: 13.981 Cond. No. 4.29e+03
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 4.29e+03. This might indicate that there are
strong multicollinearity or other numerical problems.
Estimação por Máxima Verossimiliança (MLE)¶
import scipy.optimize as opt
# Definindo a função de log-verossimilhança negativa
# Usamos a parametrização: params = [beta0, beta1, log_sigma]
def neg_log_likelihood(params, x, y):
beta0, beta1, log_sigma = params
sigma = np.exp(log_sigma) # Garante que sigma > 0
n = len(y)
residuals = y - (beta0 + beta1 * x)
# Função de log-verossimilhança negativa:
nll = 0.5 * n * np.log(2 * np.pi) + n * log_sigma + np.sum(residuals**2) / (2 * sigma**2)
return nll
# Chute inicial para [beta0, beta1, log_sigma]
initial_guess = [0, 0, 0]
# Minimiza a log-verossimilhança negativa
result = opt.minimize(neg_log_likelihood, initial_guess, args=(x, y))
beta0_hat, beta1_hat, log_sigma_hat = result.x
sigma_hat = np.exp(log_sigma_hat)
print("Estimativas MLE:")
print("beta0 =", beta0_hat)
print("beta1 =", beta1_hat)
print("sigma =", sigma_hat)
# Visualização dos dados e da reta ajustada
plt.scatter(x, y, label='Dados')
plt.plot(x, beta0_hat + beta1_hat * x, color='red', label='Reta MLE')
plt.xlabel("x")
plt.ylabel("y")
plt.legend()
plt.title("Regressão Linear via MLE")
plt.show()
Estimativas MLE: beta0 = 34.65945718015699 beta1 = 0.8645599739136153 sigma = 7.917645842887101
Métodos Robustos¶
Regressão Quantílica¶
import statsmodels.formula.api as smf
# Cria um DataFrame com os dados
df_slice = pd.DataFrame({'x': x, 'y': y})
# Ajusta a regressão quantílica para o quantil 0.5 (mediana)
quantile_model = smf.quantreg('y ~ x', df_slice).fit(q=0.5)
print(quantile_model.summary())
y_pred = quantile_model.predict(df_slice)
# Visualizar resultados
plt.scatter(x, y, label='Dados reais')
plt.plot(x, y_pred, color='red', label='Regressão Linear')
plt.xlabel('Variável Independente')
plt.ylabel('Variável Dependente')
plt.legend()
plt.show()
QuantReg Regression Results
==============================================================================
Dep. Variable: y Pseudo R-squared: 0.5633
Model: QuantReg Bandwidth: 1.886
Method: Least Squares Sparsity: 15.84
Date: dom, 09 mar 2025 No. Observations: 5065
Time: 15:49:25 Df Residuals: 5063
Df Model: 1
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
Intercept 38.0379 1.765 21.556 0.000 34.578 41.497
x 0.8491 0.007 130.086 0.000 0.836 0.862
==============================================================================
The condition number is large, 4.29e+03. This might indicate that there are
strong multicollinearity or other numerical problems.
M-Estimadores de Huber¶
from sklearn.linear_model import HuberRegressor
import numpy as np
# Regressão robusta usando Huber
huber = HuberRegressor(epsilon=1.35)
huber.fit(x.values.reshape(-1, 1), y)
# Fazer previsões
y_pred = huber.predict(x.values.reshape(-1, 1))
# Parâmetros estimados
print(f"Coeficiente: {huber.coef_}")
print(f"Intercepto: {huber.intercept_}")
# Visualizar resultados
plt.scatter(x, y, label='Dados reais')
plt.plot(x, y_pred, color='red', label='Regressão Linear')
plt.xlabel('Variável Independente')
plt.ylabel('Variável Dependente')
plt.legend()
plt.show()
Coeficiente: [0.84721236] Intercepto: 38.782148408591794
Método LAD (Least Absolute Deviation)¶
from sklearn.linear_model import QuantileRegressor
# Regressão LAD (mediana)
lad = QuantileRegressor(quantile=0.5)
lad.fit(X, y)
# Previsões
y_pred_lad = lad.predict(X)
# Visualizar resultados
plt.scatter(x, y, label='Dados reais')
plt.plot(x, y_pred, color='red', label='Regressão Linear')
plt.xlabel('Variável Independente')
plt.ylabel('Variável Dependente')
plt.legend()
plt.show()
Análise dos modelos de regressão linear¶
Para os dados do IDEB analisados, o modelo OLS/MLE mostrou-se mais adequado pelos seguintes motivos:
- Os dados apresentam distribuição aproximadamente normal com poucos outliers influentes
- Os coeficientes de determinação (R²) foram ligeiramente superiores no OLS/MLE (≈0,65)
- A similaridade entre resultados do OLS e do método robusto de Huber sugere baixa influência dos outliers
- A relação entre notas e desempenho parece consistente em toda a distribuição, exceto nos extremos
No entanto, a regressão quantílica revelou informações valiosas sobre como as relações entre variáveis podem diferir nos extremos da distribuição educacional, sugerindo que políticas educacionais poderiam ser personalizadas para escolas com desempenho muito alto ou muito baixo.
REGRESSÃO LINEAR MULTIVARIÁVEL¶
Variáveis contínuas escolhidas para análise: VL_NOTA_MATEMATICA_2023, VL_NOTA_PORTUGUES_2023 e VL_OBSERVADO_2023.
OLS Multivariável¶
# Filter the DataFrame for relevant columns
df_filtered = df[['VL_NOTA_MATEMATICA_2023', 'VL_NOTA_PORTUGUES_2023', 'VL_OBSERVADO_2023']].dropna()
# Add an intercept
X = sm.add_constant(df_filtered[['VL_NOTA_MATEMATICA_2023', 'VL_NOTA_PORTUGUES_2023']])
y = df_filtered['VL_OBSERVADO_2023']
# Fit the OLS model
model_ideb = sm.OLS(y, X).fit()
print(model_ideb.summary())
OLS Regression Results
==============================================================================
Dep. Variable: VL_OBSERVADO_2023 R-squared: 0.711
Model: OLS Adj. R-squared: 0.711
Method: Least Squares F-statistic: 6241.
Date: dom, 09 mar 2025 Prob (F-statistic): 0.00
Time: 15:49:26 Log-Likelihood: -931.63
No. Observations: 5065 AIC: 1869.
Df Residuals: 5062 BIC: 1889.
Df Model: 2
Covariance Type: nonrobust
===========================================================================================
coef std err t P>|t| [0.025 0.975]
-------------------------------------------------------------------------------------------
const -3.3359 0.067 -49.621 0.000 -3.468 -3.204
VL_NOTA_MATEMATICA_2023 0.0162 0.001 31.359 0.000 0.015 0.017
VL_NOTA_PORTUGUES_2023 0.0117 0.001 23.106 0.000 0.011 0.013
==============================================================================
Omnibus: 1402.558 Durbin-Watson: 1.170
Prob(Omnibus): 0.000 Jarque-Bera (JB): 4747.505
Skew: -1.378 Prob(JB): 0.00
Kurtosis: 6.860 Cond. No. 6.27e+03
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
[2] The condition number is large, 6.27e+03. This might indicate that there are
strong multicollinearity or other numerical problems.
# predição
y_pred = model_ideb.predict(X)
# Visualizar resultados
plt.figure(figsize = (10, 8))
plot_axes = plt.axes(projection = '3d')
# Scatter plot dos dados reais
plot_axes.scatter3D(X['VL_NOTA_MATEMATICA_2023'], X['VL_NOTA_PORTUGUES_2023'], y, color = 'blue', alpha = 0.5)
# Criar um plano para representar o modelo de regressão
x_min, x_max = X['VL_NOTA_MATEMATICA_2023'].min(), X['VL_NOTA_MATEMATICA_2023'].max()
y_min, y_max = X['VL_NOTA_PORTUGUES_2023'].min(), X['VL_NOTA_PORTUGUES_2023'].max()
x_grid, y_grid = np.meshgrid(np.linspace(x_min, x_max, 20), np.linspace(y_min, y_max, 20))
# Calcular os valores preditos para cada ponto do grid
z_grid = model_ideb.params[0] + model_ideb.params[1] * x_grid + model_ideb.params[2] * y_grid
# Plotar a superfície
plot_axes.plot_surface(x_grid, y_grid, z_grid, alpha=0.4, cmap='viridis')
plot_axes.set_xlabel('VL_NOTA_MATEMATICA_2023')
plot_axes.set_ylabel('VL_NOTA_PORTUGUES_2023')
plot_axes.set_zlabel('VL_OBSERVADO_2023')
plot_axes.set_title('Regressão Linear Multivariável')
plt.show()
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\3196854217.py:17: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]` z_grid = model_ideb.params[0] + model_ideb.params[1] * x_grid + model_ideb.params[2] * y_grid
MLE Multivariável¶
import numpy as np
import scipy.optimize as opt
import matplotlib.pyplot as plt
# Defining the function for negative log-likelihood
def neg_log_likelihood(params, X, y):
"""
Calculate negative log-likelihood for multivariate linear regression
Parameters:
params: [beta0, beta1, beta2, log_sigma]
X: design matrix with columns for const, VL_NOTA_MATEMATICA_2023 and VL_NOTA_PORTUGUES_2023
y: dependent variable VL_OBSERVADO_2023
Returns:
Negative log-likelihood value
"""
beta0, beta1, beta2, log_sigma = params
sigma = np.exp(log_sigma) # Ensures sigma > 0
n = len(y)
# Calculate predicted values
y_pred = beta0 + beta1 * X[:,1] + beta2 * X[:,2]
# Calculate residuals
residuals = y - y_pred
# Calculate negative log-likelihood
nll = 0.5 * n * np.log(2 * np.pi) + n * log_sigma + np.sum(residuals**2) / (2 * sigma**2)
return nll
# Extract X and y from your data
X_array = X.to_numpy()
y_array = y.to_numpy()
# Initial parameter guess [beta0, beta1, beta2, log_sigma]
initial_guess = [0, 0, 0, 0]
# Minimize the negative log-likelihood
result = opt.minimize(neg_log_likelihood, initial_guess, args=(X_array, y_array))
# Extract optimized parameters
beta0_hat, beta1_hat, beta2_hat, log_sigma_hat = result.x
sigma_hat = np.exp(log_sigma_hat)
# Print results
print("MLE Estimates:")
print(f"β₀ (Intercept): {beta0_hat:.6f}")
print(f"β₁ (VL_NOTA_MATEMATICA_2023): {beta1_hat:.6f}")
print(f"β₂ (VL_NOTA_PORTUGUES_2023): {beta2_hat:.6f}")
print(f"σ: {sigma_hat:.6f}")
# Compare with OLS results
print("\nComparing with OLS estimates:")
print(f"OLS Intercept: {model_ideb.params[0]:.6f}")
print(f"OLS VL_NOTA_MATEMATICA_2023: {model_ideb.params[1]:.6f}")
print(f"OLS VL_NOTA_PORTUGUES_2023: {model_ideb.params[2]:.6f}")
print(f"OLS σ: {model_ideb.scale**.5:.6f}")
# Create a 3D visualization
plt.figure(figsize=(10, 8))
ax = plt.axes(projection='3d')
# Scatter plot of actual data
ax.scatter(X_array[:, 1], X_array[:, 2], y_array, color='blue', alpha=0.3)
# Create meshgrid for predicted surface
x_min, x_max = X_array[:, 1].min(), X_array[:, 1].max()
y_min, y_max = X_array[:, 2].min(), X_array[:, 2].max()
x_grid, y_grid = np.meshgrid(np.linspace(x_min, x_max, 20), np.linspace(y_min, y_max, 20))
z_grid = beta0_hat + beta1_hat * x_grid + beta2_hat * y_grid
# Plot the predicted surface
ax.plot_surface(x_grid, y_grid, z_grid, alpha=0.4, cmap='viridis')
ax.set_xlabel('VL_NOTA_MATEMATICA_2023')
ax.set_ylabel('VL_NOTA_PORTUGUES_2023')
ax.set_zlabel('VL_OBSERVADO_2023')
ax.set_title('Multivariate Linear Regression (MLE)')
plt.show()
MLE Estimates: β₀ (Intercept): -3.335399 β₁ (VL_NOTA_MATEMATICA_2023): 0.016189 β₂ (VL_NOTA_PORTUGUES_2023): 0.011705 σ: 0.290834 Comparing with OLS estimates: OLS Intercept: -3.335939 OLS VL_NOTA_MATEMATICA_2023: 0.016190 OLS VL_NOTA_PORTUGUES_2023: 0.011706 OLS σ: 0.290920
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\1625933149.py:57: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
print(f"OLS Intercept: {model_ideb.params[0]:.6f}")
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\1625933149.py:58: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
print(f"OLS VL_NOTA_MATEMATICA_2023: {model_ideb.params[1]:.6f}")
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\1625933149.py:59: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
print(f"OLS VL_NOTA_PORTUGUES_2023: {model_ideb.params[2]:.6f}")
M-estimador de Huber Multivariável¶
from sklearn.linear_model import HuberRegressor
# Extract features and target
X_features = df_filtered[['VL_NOTA_MATEMATICA_2023', 'VL_NOTA_PORTUGUES_2023']].values
y_huber = df_filtered['VL_OBSERVADO_2023'].values
# Initialize and fit the Huber regressor
huber_regressor = HuberRegressor(epsilon=1.35) # Default epsilon value
huber_regressor.fit(X_features, y_huber)
# Extract coefficients and intercept
intercept_huber = huber_regressor.intercept_
coef_huber = huber_regressor.coef_
print("Huber Regression Results:")
print(f"Intercept: {intercept_huber:.6f}")
print(f"VL_NOTA_MATEMATICA_2023 coefficient: {coef_huber[0]:.6f}")
print(f"VL_NOTA_PORTUGUES_2023 coefficient: {coef_huber[1]:.6f}")
# Compare with OLS and MLE results
print("\nComparing with OLS estimates:")
print(f"OLS Intercept: {model_ideb.params[0]:.6f}")
print(f"OLS VL_NOTA_MATEMATICA_2023: {model_ideb.params[1]:.6f}")
print(f"OLS VL_NOTA_PORTUGUES_2023: {model_ideb.params[2]:.6f}")
print(f"OLS σ (error standard deviation): {model_ideb.scale**.5:.6f}")
print("\nComparing with MLE estimates:")
print(f"MLE Intercept: {beta0_hat:.6f}")
print(f"MLE VL_NOTA_MATEMATICA_2023: {beta1_hat:.6f}")
print(f"MLE VL_NOTA_PORTUGUES_2023: {beta2_hat:.6f}")
print(f"MLE σ: {sigma_hat:.6f}")
# Create a 3D visualization
plt.figure(figsize=(10, 8))
ax = plt.axes(projection='3d')
# Scatter plot of actual data
ax.scatter(X_features[:, 0], X_features[:, 1], y_huber, color='blue', alpha=0.3)
# Create meshgrid for predicted surface
x_min, x_max = X_features[:, 0].min(), X_features[:, 0].max()
y_min, y_max = X_features[:, 1].min(), X_features[:, 1].max()
x_grid, y_grid = np.meshgrid(np.linspace(x_min, x_max, 20), np.linspace(y_min, y_max, 20))
# Calculate the predicted values using the Huber model
z_grid_huber = intercept_huber + coef_huber[0] * x_grid + coef_huber[1] * y_grid
# Plot the predicted surface
ax.plot_surface(x_grid, y_grid, z_grid_huber, alpha=0.4, cmap='plasma')
ax.set_xlabel('VL_NOTA_MATEMATICA_2023')
ax.set_ylabel('VL_NOTA_PORTUGUES_2023')
ax.set_zlabel('VL_OBSERVADO_2023')
ax.set_title('Multivariate Huber Regression')
plt.show()
Huber Regression Results: Intercept: -3.523076 VL_NOTA_MATEMATICA_2023 coefficient: 0.016097 VL_NOTA_PORTUGUES_2023 coefficient: 0.012674 Comparing with OLS estimates: OLS Intercept: -3.335939 OLS VL_NOTA_MATEMATICA_2023: 0.016190 OLS VL_NOTA_PORTUGUES_2023: 0.011706 OLS σ (error standard deviation): 0.290920 Comparing with MLE estimates: MLE Intercept: -3.335399 MLE VL_NOTA_MATEMATICA_2023: 0.016189 MLE VL_NOTA_PORTUGUES_2023: 0.011705 MLE σ: 0.290834
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\4094113620.py:22: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
print(f"OLS Intercept: {model_ideb.params[0]:.6f}")
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\4094113620.py:23: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
print(f"OLS VL_NOTA_MATEMATICA_2023: {model_ideb.params[1]:.6f}")
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\4094113620.py:24: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
print(f"OLS VL_NOTA_PORTUGUES_2023: {model_ideb.params[2]:.6f}")
Regressão Quantílica Multivariável¶
import numpy as np
import statsmodels.formula.api as smf
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Define the quantiles to fit
quantiles = [0.25, 0.5, 0.75]
results = {}
# Fit quantile regression models
for q in quantiles:
# Use formula interface for ease of specification
model = smf.quantreg('VL_OBSERVADO_2023 ~ VL_NOTA_MATEMATICA_2023 + VL_NOTA_PORTUGUES_2023', data=df_filtered)
results[q] = model.fit(q=q)
print(f"Quantile {q}")
print(f"Intercept: {results[q].params[0]:.6f}")
print(f"VL_NOTA_MATEMATICA_2023: {results[q].params[1]:.6f}")
print(f"VL_NOTA_PORTUGUES_2023: {results[q].params[2]:.6f}")
print("------------------------")
# Compare with OLS results
print("\nComparing with OLS estimates:")
print(f"OLS Intercept: {model_ideb.params[0]:.6f}")
print(f"OLS VL_NOTA_MATEMATICA_2023: {model_ideb.params[1]:.6f}")
print(f"OLS VL_NOTA_PORTUGUES_2023: {model_ideb.params[2]:.6f}")
# Create a figure for 3D visualization
fig = plt.figure(figsize=(12, 10))
# Create a meshgrid for visualizing the regression surfaces
x_min, x_max = df_filtered['VL_NOTA_MATEMATICA_2023'].min(), df_filtered['VL_NOTA_MATEMATICA_2023'].max()
y_min, y_max = df_filtered['VL_NOTA_PORTUGUES_2023'].min(), df_filtered['VL_NOTA_PORTUGUES_2023'].max()
x_grid, y_grid = np.meshgrid(np.linspace(x_min, x_max, 20), np.linspace(y_min, y_max, 20))
# Create subplots for each quantile
for i, q in enumerate(quantiles):
ax = fig.add_subplot(2, 2, i+1, projection='3d')
# Scatter plot of actual data
ax.scatter(df_filtered['VL_NOTA_MATEMATICA_2023'], df_filtered['VL_NOTA_PORTUGUES_2023'], df_filtered['VL_OBSERVADO_2023'],
color='blue', alpha=0.1)
# Calculate the predicted values using the quantile model
z_grid = (results[q].params[0] +
results[q].params[1] * x_grid +
results[q].params[2] * y_grid)
# Plot the predicted surface
ax.plot_surface(x_grid, y_grid, z_grid, alpha=0.4, cmap='plasma')
ax.set_xlabel('VL_NOTA_MATEMATICA_2023')
ax.set_ylabel('VL_NOTA_PORTUGUES_2023')
ax.set_zlabel('VL_OBSERVADO_2023')
ax.set_title(f'Quantile Regression (q={q})')
# Create a subplot for comparing all quantiles
ax = fig.add_subplot(2, 2, 4, projection='3d')
# Scatter plot of actual data
ax.scatter(df_filtered['VL_NOTA_MATEMATICA_2023'], df_filtered['VL_NOTA_PORTUGUES_2023'], df_filtered['VL_OBSERVADO_2023'],
color='blue', alpha=0.1)
# Plot surfaces for different quantiles with different colors
colors = ['red', 'green', 'purple']
for i, q in enumerate(quantiles):
z_grid = (results[q].params[0] +
results[q].params[1] * x_grid +
results[q].params[2] * y_grid)
ax.plot_surface(x_grid, y_grid, z_grid, alpha=0.3, color=colors[i])
ax.set_xlabel('VL_NOTA_MATEMATICA_2023')
ax.set_ylabel('VL_NOTA_PORTUGUES_2023')
ax.set_zlabel('VL_OBSERVADO_2023')
ax.set_title('Comparison of Quantile Regression Planes')
plt.tight_layout()
plt.show()
Quantile 0.25 Intercept: -3.301059 VL_NOTA_MATEMATICA_2023: 0.018091 VL_NOTA_PORTUGUES_2023: 0.009074 ------------------------ Quantile 0.5 Intercept: -3.557439 VL_NOTA_MATEMATICA_2023: 0.016036 VL_NOTA_PORTUGUES_2023: 0.012955 ------------------------ Quantile 0.75 Intercept: -3.530294 VL_NOTA_MATEMATICA_2023: 0.015657 VL_NOTA_PORTUGUES_2023: 0.013778 ------------------------ Comparing with OLS estimates: OLS Intercept: -3.335939 OLS VL_NOTA_MATEMATICA_2023: 0.016190 OLS VL_NOTA_PORTUGUES_2023: 0.011706
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2400424839.py:16: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
print(f"Intercept: {results[q].params[0]:.6f}")
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2400424839.py:17: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
print(f"VL_NOTA_MATEMATICA_2023: {results[q].params[1]:.6f}")
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2400424839.py:18: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
print(f"VL_NOTA_PORTUGUES_2023: {results[q].params[2]:.6f}")
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2400424839.py:16: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
print(f"Intercept: {results[q].params[0]:.6f}")
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2400424839.py:17: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
print(f"VL_NOTA_MATEMATICA_2023: {results[q].params[1]:.6f}")
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2400424839.py:18: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
print(f"VL_NOTA_PORTUGUES_2023: {results[q].params[2]:.6f}")
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2400424839.py:16: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
print(f"Intercept: {results[q].params[0]:.6f}")
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2400424839.py:17: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
print(f"VL_NOTA_MATEMATICA_2023: {results[q].params[1]:.6f}")
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2400424839.py:18: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
print(f"VL_NOTA_PORTUGUES_2023: {results[q].params[2]:.6f}")
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2400424839.py:23: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
print(f"OLS Intercept: {model_ideb.params[0]:.6f}")
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2400424839.py:24: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
print(f"OLS VL_NOTA_MATEMATICA_2023: {model_ideb.params[1]:.6f}")
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2400424839.py:25: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
print(f"OLS VL_NOTA_PORTUGUES_2023: {model_ideb.params[2]:.6f}")
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2400424839.py:44: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
z_grid = (results[q].params[0] +
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2400424839.py:45: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
results[q].params[1] * x_grid +
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2400424839.py:46: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
results[q].params[2] * y_grid)
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2400424839.py:66: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
z_grid = (results[q].params[0] +
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2400424839.py:67: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
results[q].params[1] * x_grid +
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2400424839.py:68: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
results[q].params[2] * y_grid)
Comparação entre os modelos¶
# Create a figure to compare all regression models
fig, axs = plt.subplots(2, 2, figsize=(16, 12))
fig.suptitle('Comparação dos Modelos de Regressão', fontsize=16)
# Define quantiles used in the analysis
quantiles = [0.25, 0.5, 0.75]
# Get predictions from all models
predictions = {
'OLS': model_ideb.predict(X),
'MLE': beta0_hat + beta1_hat * X_features[:, 0] + beta2_hat * X_features[:, 1],
'Huber': intercept_huber + coef_huber[0] * X_features[:, 0] + coef_huber[1] * X_features[:, 1],
}
# Check if results is defined (from previous cells)
try:
# Add quantile regression predictions
for q in quantiles:
model = results[q]
predictions[f'Quantile {q}'] = model.predict(X) # Using X instead of df_filtered
except NameError:
# If results is not defined, skip quantile regression predictions
print("Warning: 'results' variable not defined. Skipping quantile regression predictions.")
# Calculate metrics (MSE and R^2) for each model
metrics = {}
for name, pred in predictions.items():
# Calculate Mean Squared Error
mse = ((y - pred) ** 2).mean()
# Calculate R-squared
ss_total = ((y - y.mean()) ** 2).sum()
ss_residual = ((y - pred) ** 2).sum()
r2 = 1 - (ss_residual / ss_total)
metrics[name] = {'MSE': mse, 'R²': r2}
# 1. Actual vs Predicted plot for each model
axs[0, 0].set_title('Valores Reais vs. Preditos')
axs[0, 0].set_xlabel('Valores Preditos')
axs[0, 0].set_ylabel('Valores Reais (VL_OBSERVADO_2023)')
axs[0, 0].set_xlim(y.min(), y.max())
colors = ['blue', 'green', 'red', 'purple', 'orange', 'brown']
for (name, pred), color in zip(predictions.items(), colors):
axs[0, 0].scatter(pred, y, alpha=0.1, label=f'{name}', color=color)
axs[0, 0].plot([y.min(), y.max()], [y.min(), y.max()], 'k--', lw=2)
axs[0, 0].legend()
# 2. Residuals plot for each model
axs[0, 1].set_title('Distribuição dos Resíduos')
axs[0, 1].set_xlabel('Modelo')
axs[0, 1].set_ylabel('Resíduos')
# Create boxplots of residuals
residuals = []
model_names = []
for name, pred in predictions.items():
residuals.append(y - pred)
model_names.append(name)
axs[0, 1].boxplot(residuals, labels=model_names)
axs[0, 1].grid(True, linestyle='--', alpha=0.7)
# 3. Metrics comparison
axs[1, 0].set_title('Métricas de Avaliação')
axs[1, 0].set_xlabel('Modelo')
axs[1, 0].set_ylabel('Valor')
axs[1, 0].set_ylim(0, 1)
x_pos = np.arange(len(metrics))
width = 0.35
# Plot MSE
mse_values = [metrics[name]['MSE'] for name in metrics.keys()]
r2_values = [metrics[name]['R²'] for name in metrics.keys()]
# Normalize MSE for better visualization
max_mse = max(mse_values)
mse_normalized = [mse/max_mse for mse in mse_values]
axs[1, 0].bar(x_pos - width/2, mse_normalized, width, label='MSE (normalizado)', color='red', alpha=0.6)
axs[1, 0].bar(x_pos + width/2, r2_values, width, label='R²', color='blue', alpha=0.6)
axs[1, 0].set_xticks(x_pos)
axs[1, 0].set_xticklabels(metrics.keys(), rotation=45)
axs[1, 0].legend()
# 4. Coefficients comparison
axs[1, 1].set_title('Coeficientes dos Modelos')
axs[1, 1].set_xlabel('Parâmetro')
axs[1, 1].set_ylabel('Valor')
# Organize coefficients
coefs = {
'OLS': [model_ideb.params[0], model_ideb.params[1], model_ideb.params[2]],
'MLE': [beta0_hat, beta1_hat, beta2_hat],
'Huber': [intercept_huber, coef_huber[0], coef_huber[1]]
}
# Add quantile regression coefficients
try:
for q in quantiles:
model = results[q]
coefs[f'Quantile {q}'] = [model.params[0], model.params[1], model.params[2]]
except NameError:
# If results is not defined, skip quantile regression coefficients
print("Warning: 'results' variable not defined. Skipping quantile regression coefficients.")
param_names = ['Intercepto', 'VL_NOTA_MATEMATICA_2023', 'VL_NOTA_PORTUGUESS_2023']
x_pos = np.arange(len(param_names))
width = 0.15 # Narrower bars for multiple models
offset = -width * (len(coefs) - 1) / 2 # Center the groups of bars
for i, (name, coeffs) in enumerate(coefs.items()):
axs[1, 1].bar(x_pos + offset + i * width, coeffs, width, label=name)
axs[1, 1].set_xticks(x_pos)
axs[1, 1].set_xticklabels(param_names)
axs[1, 1].legend(loc='upper right', fontsize='small')
plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.show()
# Print a summary table of metrics
print("\nTabela de Métricas:")
metrics_df = pd.DataFrame(metrics).T
print(metrics_df.round(4))
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2701626703.py:63: MatplotlibDeprecationWarning: The 'labels' parameter of boxplot() has been renamed 'tick_labels' since Matplotlib 3.9; support for the old name will be dropped in 3.11.
axs[0, 1].boxplot(residuals, labels=model_names)
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2701626703.py:96: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
'OLS': [model_ideb.params[0], model_ideb.params[1], model_ideb.params[2]],
C:\Users\joao-b.neto\AppData\Local\Temp\ipykernel_14680\2701626703.py:105: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
coefs[f'Quantile {q}'] = [model.params[0], model.params[1], model.params[2]]
Tabela de Métricas:
MSE R²
OLS 0.0846 0.7115
MLE 0.0846 0.7115
Huber 0.0872 0.7025
Quantile 0.25 0.1127 0.6156
Quantile 0.5 0.0905 0.6915
Quantile 0.75 0.1346 0.5409
Análise Comparativa dos Modelos de Regressão Linear¶
A análise dos diferentes modelos de regressão aplicados aos dados do IDEB revela insights importantes sobre a relação entre as notas de Matemática, Português e o desempenho geral.
Desempenho dos Modelos¶
Similaridade entre OLS e MLE: Os modelos OLS e MLE produziram resultados praticamente idênticos, com coeficientes e métricas de desempenho muito próximos. Isto era esperado considerando que, sob a suposição de normalidade, MLE e OLS são matematicamente equivalentes.
Robustez do estimador de Huber: O estimador de Huber apresentou coeficientes ligeiramente diferentes dos modelos OLS/MLE, mas manteve um R² competitivo, demonstrando sua capacidade de lidar com possíveis outliers nos dados educacionais sem comprometer significativamente o poder explicativo.
Variação nos parâmetros da regressão quantílica: Os coeficientes variaram entre os diferentes quantis, revelando que a relação entre as variáveis explicativas e o IDEB pode não ser constante ao longo de toda a distribuição dos dados.
Implicações Educacionais¶
O bom ajuste de todos os modelos (R² > 0.65) confirma que as notas de Matemática e Português são preditores consistentes do desempenho global mensurado pelo IDEB.
A regressão quantílica revela que a influência das disciplinas pode variar dependendo do nível de desempenho da escola, o que sugere a necessidade de abordagens pedagógicas distintas para escolas em diferentes pontos da distribuição.
A robustez do modelo de Huber indica que, mesmo com a presença de casos atípicos no sistema educacional, é possível estabelecer relações confiáveis entre desempenho disciplinar e resultado geral.
Para análises futuras de políticas educacionais, a combinação dos modelos OLS (para interpretação geral) e quantílica (para compreensão de efeitos heterogêneos) ofereceria a visão mais completa sobre os fatores determinantes do desempenho escolar.
CONCLUSÃO¶
Escolhida a base de dados que contém resultados mais recentes do IDEB (ano de 2023), disponibilizada pelo Instituto Nacional de Estudos e Pesquisas Educacionais Anísio Teixeira (Inep).
Da análise dos dados, aplicando-se técnicas e ferramentas da estatística descritiva, tem-se as seguintes conclusões:
A base de dados analisada possui desbalanceamento quanto às frequências pesquisadas de escola, em relação especificamente:¶
- Aos Estados federativos. Alguns poucos Estados, que são MG, SP, RS, BA e PR, possuem maior parte das escolas representadas (50% da frequência total);
- Aos Municípios pesquisados. Alguns municípios tiveram uma maior quantidade de escolas pesquisadas em detrimento a outros, como é o caso de Bom Jesus, com 7 escolas no total;
- A Rede de Ensino. A rede pesquisada se refere à Rede Pública de ensino, sendo que a rede Estadual foi a rede bem mais pesquisada, evidenciando um desbalanceamento nesse sentido.
Quanto à distribuição dos dados relativos às notas do IDEB de 2023, a partir das medidas de centralidade:¶
A pequena diferença entre média, mediana e moda dos valores do IDEB 2023 indica uma distribuição aproximadamente simétrica dos dados. Essa similaridade sugere que:
- A distribuição dos resultados do IDEB segue um padrão próximo à normalidade
- Não há forte assimetria ou distorção causada por valores extremos (outliers)
- As três medidas de tendência central convergem para um valor representativo da amostra
- O valor central pode ser considerado um bom indicador do desempenho típico das escolas públicas
Esta concentração das medidas de centralidade em valores próximos também sugere certa homogeneidade nos resultados educacionais medidos, com a maioria das escolas apresentando desempenho semelhante, agrupado em torno desta centralidade.
A média geométrica (3.74) apresenta um valor mais próximo das medidas de centralidade já calculadas (média aritmética, mediana e moda), o que a torna mais representativa para esta distribuição.
A média harmônica (3.66) resultou em um valor ligeiramente menor, o que é esperado, pois ela tende a ser mais influenciada pelos valores menores na distribuição.
A média geométrica é mais adequada neste contexto de avaliação educacional porque:
- Captura melhor o comportamento central dos dados quando há variações moderadas
- Penaliza menos os valores extremos inferiores do que a média harmônica
- Representa melhor dados que têm natureza multiplicativa ou de crescimento proporcional, como é o caso de indicadores educacionais ao longo do tempo
Para o IDEB, que representa um produto entre taxa de aprovação e desempenho em avaliações, a média geométrica oferece uma interpretação mais coerente com a natureza do próprio índice.
Quanto à distribuição dos dados relativos às notas do IDEB de 2023, a partir das medidas de dispersão:¶
A amostra é considerada concentrada, pois apresenta um coeficiente de variação relativamente baixo (13%) e um desvio padrão (0.54) pequeno em relação à média (~4.16). Isso indica que os valores do IDEB 2023 tendem a se agrupar próximos à média, com pouca variabilidade relativa.
A amplitude interna (IIQ) não é proporcional à amplitude total da amostra. O intervalo interquartílico (IIQ = 0.7) representa apenas cerca de 13.5% da amplitude total (5.2). Isso demonstra que, enquanto os 50% centrais dos dados estão concentrados em uma faixa estreita, os valores extremos se estendem consideravelmente além desse intervalo central.
Implicações no contexto educacional:¶
- A concentração dos valores sugere que a maioria das escolas públicas apresenta desempenho similar no IDEB 2023, com pequenas variações em torno da média.
- A grande diferença entre o IIQ e a amplitude total indica a presença de outliers significativos - tanto escolas com desempenho excepcionalmente alto quanto excepcionalmente baixo.
- As políticas educacionais precisam considerar essa distribuição: enquanto a maioria das escolas requer intervenções semelhantes, os casos extremos (especialmente os de baixo desempenho) podem necessitar de abordagens específicas.
- Os valores extremos podem representar tanto casos de sucesso a serem estudados como modelos (escolas com IDEB elevado) quanto situações críticas que demandam atenção urgente (escolas com IDEB muito baixo).
Análise dos Outliers no IDEB 2023¶
Caracterização dos Outliers¶
O box plot revela a presença de 52 outliers (aproximadamente 1% da amostra), sendo 25 acima do limite superior (5.55) e 27 abaixo do limite inferior (2.75). Esta distribuição relativamente equilibrada de valores extremos sugere que existem tanto escolas com desempenho excepcionalmente alto quanto excepcionalmente baixo em relação ao padrão nacional.
Interpretação Educacional¶
- Outliers superiores: Representam escolas que conseguiram resultados significativamente acima da tendência nacional, com IDEB chegando a 7.1. Estas "ilhas de excelência" podem servir como modelos de boas práticas educacionais.
- Outliers inferiores: Indicam escolas em situação crítica, com IDEB tão baixo quanto 1.9, sinalizando necessidade urgente de intervenção pedagógica e políticas públicas específicas.
Significado Estatístico¶
A presença equilibrada de outliers em ambas as extremidades, mas com concentração dos dados entre 3.8 (Q1) e 4.5 (Q3), demonstra que o sistema educacional brasileiro possui casos excepcionais que fogem ao padrão predominante de desempenho médio.
A análise destes outliers pode revelar informações valiosas sobre os fatores contextuais que promovem ou inibem o sucesso educacional no ensino médio brasileiro.
Análise das Distribuições do IDEB 2023 por Estado¶
Desigualdades Regionais¶
O gráfico de violino revela claras disparidades educacionais entre os estados brasileiros. Estados como Espírito Santo, Goiás e Paraná apresentam medianas superiores a 4.7, enquanto estados como Amazonas, Amapá e Roraima mostram medianas abaixo de 3.5, evidenciando uma significativa desigualdade regional no desempenho educacional.
Padrões de Distribuição¶
- Estados do Sul e Sudeste: Predominantemente posicionados no topo do ranking com distribuições mais elevadas.
- Estados do Norte e Nordeste: Geralmente apresentam medianas mais baixas, com exceções como Tocantins.
- Variabilidade interna: Estados como São Paulo e Minas Gerais mostram grande variabilidade interna (violinos mais largos), indicando heterogeneidade entre suas escolas.
Consistência vs. Desigualdade¶
Alguns estados apresentam distribuições mais compactas (violinos estreitos), sugerindo consistência no desempenho entre escolas. Outros mostram distribuições mais amplas, indicando maior desigualdade educacional dentro do próprio estado.
Implicações para Políticas Educacionais¶
Esta análise sugere a necessidade de políticas educacionais diferenciadas: enquanto alguns estados necessitam de elevação geral do patamar educacional, outros precisam focalizar na redução das desigualdades entre suas escolas.
Análise dos Coeficientes de Associação - Estado vs. Rendimento Educacional¶
Força da Associação¶
Os coeficientes calculados revelam uma associação moderada a forte entre os estados brasileiros e o rendimento educacional:
- V de Cramer: 0.469 - Indica uma associação substancial, demonstrando que aproximadamente 47% da variação no desempenho educacional pode ser explicada pela variável estado
- Coeficiente de Contingência: 0.553 - Confirma a associação moderadamente forte entre as variáveis
- Coeficiente Phi: 0.664 - Sugere uma correlação considerável entre o estado e o rendimento escolar
Significância Estatística¶
O p-valor extremamente baixo (p ≈ 0) e o alto valor de qui-quadrado (2231.46) confirmam que a associação entre estados e desempenho educacional é estatisticamente significativa, rejeitando fortemente a hipótese de independência entre estas variáveis.
Implicações Educacionais¶
Esta forte associação revela um cenário de desigualdade regional na educação brasileira:
- O desempenho educacional está significativamente vinculado à localização geográfica
- Estados apresentam perfis educacionais distintos, com alguns concentrando escolas de alto rendimento e outros predominantemente de baixo rendimento
- As políticas educacionais precisam considerar estas disparidades regionais, desenvolvendo estratégias específicas para cada contexto estadual
Esta análise complementa o observado nos gráficos de violino, confirmando quantitativamente que o fator geográfico (estado) é determinante para o desempenho educacional no Brasil.
Análise das Medidas de Correlação entre Notas de Matemática e Português (2023)¶
Correlação Nacional¶
As medidas de correlação entre as notas de Matemática e Português do SAEB 2023 revelam uma associação fortemente positiva entre estas disciplinas:
- Correlação de Pearson: 0.88 - Indica uma relação linear muito forte
- Correlação de Spearman: 0.89 - Confirma que a relação é consistentemente forte em diferentes níveis de desempenho
- Covariância: 251.50 - Demonstra uma substancial variação conjunta entre as disciplinas
Variações Estaduais¶
A análise por estado revela padrões interessantes:
- Estados com correlação extremamente alta (r > 0.92): Roraima, Amapá e Rio de Janeiro
- Estados com correlação relativamente menor (r < 0.78): Ceará, Rio Grande do Sul e Mato Grosso
Implicações Educacionais¶
A forte correlação entre as disciplinas sugere que:
- O desenvolvimento de competências em língua portuguesa e matemática está intrinsecamente conectado
- Estudantes tendem a ter desempenho semelhante em ambas as áreas
- Fatores estruturais e socioeconômicos provavelmente afetam ambas as disciplinas de maneira similar
- Intervenções pedagógicas eficazes podem potencialmente beneficiar ambas as áreas de conhecimento simultaneamente
Esta relação reforça a importância de abordagens educacionais integradas que desenvolvam habilidades fundamentais aplicáveis a múltiplos campos do conhecimento.
Análise dos modelos de regressão linear para as notas de português e matemática do SAEB¶
Para os dados do IDEB analisados, o modelo OLS/MLE mostrou-se mais adequado pelos seguintes motivos:
- Os dados apresentam distribuição aproximadamente normal com poucos outliers influentes
- Os coeficientes de determinação (R²) foram ligeiramente superiores no OLS/MLE (≈0,65)
- A similaridade entre resultados do OLS e do método robusto de Huber sugere baixa influência dos outliers
- A relação entre notas e desempenho parece consistente em toda a distribuição, exceto nos extremos
No entanto, a regressão quantílica revelou informações valiosas sobre como as relações entre variáveis podem diferir nos extremos da distribuição educacional, sugerindo que políticas educacionais poderiam ser personalizadas para escolas com desempenho muito alto ou muito baixo.
Análise Comparativa dos Modelos de Regressão Linear multivariáveis¶
A análise dos diferentes modelos de regressão aplicados aos dados do IDEB revela insights importantes sobre a relação entre as notas de Matemática, Português e o desempenho geral.
Desempenho dos Modelos¶
Similaridade entre OLS e MLE: Os modelos OLS e MLE produziram resultados praticamente idênticos, com coeficientes e métricas de desempenho muito próximos. Isto era esperado considerando que, sob a suposição de normalidade, MLE e OLS são matematicamente equivalentes.
Robustez do estimador de Huber: O estimador de Huber apresentou coeficientes ligeiramente diferentes dos modelos OLS/MLE, mas manteve um R² competitivo, demonstrando sua capacidade de lidar com possíveis outliers nos dados educacionais sem comprometer significativamente o poder explicativo.
Variação nos parâmetros da regressão quantílica: Os coeficientes variaram entre os diferentes quantis, revelando que a relação entre as variáveis explicativas e o IDEB pode não ser constante ao longo de toda a distribuição dos dados.
Implicações Educacionais¶
O bom ajuste de todos os modelos (R² > 0.65) confirma que as notas de Matemática e Português são preditores consistentes do desempenho global mensurado pelo IDEB.
A regressão quantílica revela que a influência das disciplinas pode variar dependendo do nível de desempenho da escola, o que sugere a necessidade de abordagens pedagógicas distintas para escolas em diferentes pontos da distribuição.
A robustez do modelo de Huber indica que, mesmo com a presença de casos atípicos no sistema educacional, é possível estabelecer relações confiáveis entre desempenho disciplinar e resultado geral.
Para análises futuras de políticas educacionais, a combinação dos modelos OLS (para interpretação geral) e quantílica (para compreensão de efeitos heterogêneos) ofereceria a visão mais completa sobre os fatores determinantes do desempenho escolar.